package org.epidroid.epidroid.androsoul.ui;

import java.util.Iterator;

import org.epidroid.epidroid.R;
import org.epidroid.epidroid.androsoul.engine.AndroSoul;
import org.epidroid.epidroid.androsoul.engine.communication.Connection;
import org.epidroid.epidroid.androsoul.engine.contacts.ContactGroup;
import org.epidroid.epidroid.androsoul.engine.contacts.NetsoulContact;
import org.epidroid.epidroid.androsoul.engine.conversation.MessageItem;
import org.epidroid.epidroid.androsoul.engine.service.AndroSoulService;
import org.epidroid.epidroid.androsoul.engine.service.AndroSoulServiceBinder;
import org.epidroid.epidroid.androsoul.engine.service.IAndroSoulService;
import org.epidroid.epidroid.androsoul.engine.service.IAndroSoulServiceListener;
import org.epidroid.epidroid.androsoul.exceptions.AndroSoulException;
import org.epidroid.epidroid.androsoul.ui.configuration.ConfigurationActivity;
import org.epidroid.epidroid.androsoul.ui.configuration.ConfigurationUpdater;
import org.epidroid.epidroid.androsoul.ui.contactimport.ContactsImportActivity;
import org.epidroid.epidroid.androsoul.ui.contactlist.AddContactHandler;
import org.epidroid.epidroid.androsoul.ui.contactlist.AddGroupHandler;
import org.epidroid.epidroid.androsoul.ui.contactlist.ContactAdapter;
import org.epidroid.epidroid.androsoul.ui.contactlist.ContactComparatorOnLineTop;
import org.epidroid.epidroid.androsoul.ui.contactlist.ContactsListAdapter;
import org.epidroid.epidroid.androsoul.ui.contactlist.GroupAdapter;
import org.epidroid.epidroid.androsoul.ui.contactlist.HeaderView;
import org.epidroid.epidroid.androsoul.ui.contactlist.LoginForm;
import org.epidroid.epidroid.androsoul.ui.contactlist.MoveContactHandler;
import org.epidroid.epidroid.androsoul.ui.contactlist.NoContactForm;
import org.epidroid.epidroid.androsoul.ui.contactlist.RenameGroupHandler;
import org.epidroid.epidroid.androsoul.ui.contactlist.SectionedAdapter;
import org.epidroid.epidroid.androsoul.ui.contactlist.ShowContactHandler;
import org.epidroid.epidroid.androsoul.ui.contactlist.messagequeue.MessageQueue;
import org.epidroid.epidroid.androsoul.ui.contactlist.messagequeue.MessageQueueItem;
import org.epidroid.epidroid.androsoul.ui.conversation.ConversationActivity;
import org.epidroid.epidroid.androsoul.ui.conversation.ConversationsSwitcher;
import org.epidroid.epidroid.androsoul.ui.utils.ContactsLoader;
import org.epidroid.epidroid.androsoul.ui.utils.PicturesDownloader;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.HorizontalScrollView;
import android.widget.ListView;
import android.widget.TableRow;
import android.widget.Toast;

/**
 * @author conrat_a Handle the user interface for the Androsoul application. All
 *         data should be handled by the AndroSoul class. This class only
 *         initializes / updates the user interface and queries the Androsoul
 *         service.
 */
public class AndroSoulActivity extends Activity {

	// Name of the tag for the Android debug tool
	public static final String LOGCAT = "androsoul";

	private Boolean _initIsDone = false;
	// AndroSoul service
	private IAndroSoulService _service = null;
	// The list view that lists the contact groups
	private ListView _contactList = null;
	// Custom adapter that handles the contact list
	private SectionedAdapter _sectionedAdapter = null;
	// Message queue (messages received when not concerned by
	// the currently opened conversation
	private MessageQueue _messageQueue = null;
	private LoginForm _loginForm = null;
	private NoContactForm _noContactForm = null;
	private ConversationsSwitcher _conversationSwitcher = null;
	// The scrollview that contains the message queue
	// private ScrollView _scrollView = null;
	// Preferences
	private SharedPreferences _prefs = null;
	// Contact whim whom we are talking to
	private NetsoulContact _talkingTo = null;
	// Handler for the header
	private HeaderView _headerView = null;
	// Handler for the menu for adding a contact
	private AddContactHandler _addContactHandler = null;
	// Handler for the menu for adding a contact
	private MoveContactHandler _moveContactHandler = null;
	// Handler for the menu for adding a group
	private AddGroupHandler _addGroupHandler = null;
	// Handler for the menu for renaming a group
	private RenameGroupHandler _renameGroupHandler = null;
	// Handler for the menu for showing / deleting a contact
	private ShowContactHandler _showContactHandler = null;
	private ProgressDialog _progressDialog = null;

	private ServiceConnection _serviceConnection = null;

	/**
	 * Called when the activity is first created Instantiate the AndroSoul main
	 * class
	 * */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		Log.v(LOGCAT, "ANDROSOULACTIVITY CREATED");

		// TODO: display a loading message ?

		// Start AndroSoul service
		bindWithAndroSoulService();
		_prefs = PreferenceManager.getDefaultSharedPreferences(this);
	}

	@Override
	protected void onDestroy() {
		unbindService(_serviceConnection);

		super.onDestroy();
	}

	/**
	 * Bind this activity with the AndroSoul service
	 */
	private void bindWithAndroSoulService() {
		Intent intent = new Intent(this, AndroSoulService.class);

		final IAndroSoulServiceListener listener = new IAndroSoulServiceListener() {
			public void dataChanged(final Message data) {
				AndroSoulActivity.this.runOnUiThread(new Runnable() {
					public void run() {
						ReceiveAction(data);
					}
				});
			}
		};

		_serviceConnection = new ServiceConnection() {
			public void onServiceConnected(ComponentName name,
					IBinder servicebinder) {
				IAndroSoulService service = ((AndroSoulServiceBinder) servicebinder)
						.getService();
				service.addListener(listener);
				_service = service;
				_service.enableNotifs(false);
				// We are done binding, now init the gui
				init();
			}

			public void onServiceDisconnected(ComponentName name) {
				Log.i(AndroSoul.LOGCAT, "Service has disconnected!");
			}
		};

		bindService(intent, _serviceConnection, Context.BIND_AUTO_CREATE);
	}

	/**
	 * Initialize the user interface. Start authentication if not already done.
	 */
	private void init() {
		// Retrieve data from configuration
		ConfigurationUpdater.process(_service, getBaseContext());

		setContentView(R.layout.androsoul);
		_loginForm = new LoginForm(this);

		if (!_prefs.getString("login", "").equals("")
				&& _prefs.getBoolean("autologin", false)
				&& _service.getConnectionState() == Connection.State.OFF) {
			connect();
		}

		_contactList = (ListView) findViewById(R.id.groupList);
		_noContactForm = new NoContactForm(this, _service);
		setContactsAdapter();
		setContactClickListener();
		setContactLongClickListener();
		initMessageQueue();
		initConversationSwitcher();
		_headerView = new HeaderView(_service, this);
		_headerView.update();
		_addContactHandler = new AddContactHandler(_service, this);
		_moveContactHandler = new MoveContactHandler(_service, this);
		_addGroupHandler = new AddGroupHandler(_service, this);
		_renameGroupHandler = new RenameGroupHandler(_service, this);
		_showContactHandler = new ShowContactHandler(_service, this);

		if (_service.getConnectionState() == Connection.State.AUTHENTIFIED) {
			Log.v(LOGCAT, "ALREADY CONNECTED");
			initContactsAdapter();
			_loginForm.hide();
		}

		_initIsDone = true;
	}

	private void connect() {
		_loginForm.hide();
		showProgressDialog(getString(R.string.connecting));
		if (!_service.connect(_prefs.getString("login", ""),
				_prefs.getString("pass", ""))) {
			clearProgressDialog();
		}
	}

	/**
	 * Called when the server sends the response after a login query. If the
	 * result code is 2, the authentication is successful.
	 * 
	 * @param msg
	 *            Message containing the result code
	 */
	private void loginResult(Message msg) {
		if (msg.arg1 == 2) {
			_messageQueue.flushAndroSoulMessages();
			loadContacts();
		} else {
			_messageQueue.addAndroSoulMessage(msg.getData().getString("msg"));
			showToast(getString(R.string.password_wrong));
			_loginForm.show();
		}
		_headerView.update();
		clearProgressDialog();
	}

	@Override
	protected void onPause() {
		super.onPause();
		if (_service != null)
			_service.enableNotifs(true);
	}

	@Override
	protected void onResume() {
		super.onResume();

		Log.v(LOGCAT, "ANDROSOULACTIVITY RESUMED");

		if (_sectionedAdapter != null) {
			_sectionedAdapter.notifyDataSetChanged();
		}

		/*
		 * Update the conversation switcher
		 */
		if (_conversationSwitcher != null) {
			_conversationSwitcher.update();
		}

		/*
		 * Clear the message queue with the messages this contact sent earlier
		 */
		if (_talkingTo != null) {
			// TODO: not working, deletes everything...
			if (_messageQueue != null)
				_messageQueue.flush(_talkingTo.getName());
			_talkingTo = null;
		}

		// Disable notifications
		if (_service != null)
			_service.enableNotifs(false);

		// Update the header view
		if (_headerView != null)
			_headerView.update();

		// Show / hide the login form depending
		// on the connection state
		if (_initIsDone) {
			if (_service.getConnectionState() == Connection.State.AUTHENTIFIED) {
				_loginForm.hide();
				_noContactForm.update();
			} else {
				_loginForm.show();
				_noContactForm.hide();
			}
		}
	};

	/**
	 * Initialize the conversation switcher.
	 */
	private void initConversationSwitcher() {
		HorizontalScrollView scroll = (HorizontalScrollView) findViewById(R.id.ScrollViewOpenedConv);
		TableRow row = (TableRow) scroll.findViewById(R.id.rowOpenConv);
		_conversationSwitcher = new ConversationsSwitcher(_service, this, row,
				scroll);
	}

	/**
	 * Initialize the Message queue.
	 */
	private void initMessageQueue() {
		/*
		 * _scrollView = (ScrollView) findViewById(R.id.ScrollViewMessageQueue);
		 * TableLayout tabLayout = (TableLayout) _scrollView
		 * .findViewById(R.id.tab01);
		 */

		_messageQueue = new MessageQueue(this);
	}

	/**
	 * Set the handler that will be later passed to the listener. Set the
	 * actions that must be done according to the actions received.
	 */
	private void ReceiveAction(Message msg) {
		switch (msg.what) {
		case AndroSoul.EXT_USER_LOG:
			loginResult(msg);
			break;
		case AndroSoul.LIST_USERS:
		case AndroSoul.CONTACT_STATE_UPDATE:
		case AndroSoul.LOGIN:
		case AndroSoul.CONTACT_LOGOUT:
			// if some contacts changed their state, just update the list of
			// contacts
			updateContactsListAdapter();
			break;
		case AndroSoul.RECEIVE_MESSAGE:
			receivedMessage(msg);
			break;
		case AndroSoul.WIZZ:
			String c = (String) msg.obj;
			if (c != null)
				showToast("received wizz from " + c);
			break;
		case AndroSoul.EXCEPTION:
			clearContactList();
			_messageQueue.addAndroSoulMessage(((AndroSoulException) msg.obj)
					.getMessage());
			clearProgressDialog();
			if (_service.getConnectionState() == Connection.State.OFF)
				_loginForm.show();
			break;
		case AndroSoul.DECONNECTED:
			clearContactList();
			_messageQueue
					.addAndroSoulMessage(getString(R.string.lost_connection));
			break;
		default:
			break;
		}
	}

	/**
	 * Called when receiving a message from a contact
	 * 
	 * @param msg
	 *            Message received
	 */
	public void receivedMessage(Message msg) {
		MessageItem message = (MessageItem) msg.obj;

		/*
		 * If this is the contact we are talking to, the message is handled by
		 * the conversation activity, do nothing
		 */
		if ((_talkingTo != null)
				&& (message.getAuthor().equals(_talkingTo.getName())))
			return;

		NetsoulContact c = _service.getContactFromName(message.getAuthor());
		// Otherwise, add the message to the message queue
		_messageQueue.add(new MessageQueueItem(c, message));
		// update the Conversation switcher
		_conversationSwitcher.update();
	}

	/**
	 * Load the contacts from the configuration file
	 */
	public void loadContacts() {
		ContactsLoader.loadContacts(_service, this);
		initContactsAdapter();
	}

	/**
	 * Save the contacts to the configuration file
	 */
	public void saveContacts() {
		ContactsLoader.saveContacts(_service, this);
	}

	/**
	 * Create the custom adapter. Add the group of contacts, bind it to the
	 * ListView.
	 */
	public void setContactsAdapter() {
		GroupAdapter headers = new GroupAdapter(this,
				R.layout.androsoul_group_header, _service.getGroupsList());
		_sectionedAdapter = new ContactsListAdapter(headers);
		_contactList.setAdapter(_sectionedAdapter);
	}

	public void initContactsAdapter() {
		_sectionedAdapter.clear();
		Iterator<ContactGroup> it = _service.getGroupsList().iterator();
		while (it.hasNext()) {
			ContactGroup tmp = it.next();
			ContactAdapterAddGroup(tmp);
		}
		_sectionedAdapter.notifyDataSetChanged();

		// In case some pictures were not downloaded yet
		updateContactsPictures();
	}

	/**
	 * Add the given group to the contact list adapter
	 * 
	 * @param group
	 *            group to be added
	 */
	public void ContactAdapterAddGroup(ContactGroup group) {

		_sectionedAdapter.addSection(group.getName(), new ContactAdapter(this,
				R.layout.androsoul_list_item, group.getList()));
	}

	/**
	 * Notify the contact list adapter that the data has changed
	 */
	public void updateContactsListAdapter() {
		_sectionedAdapter.notifyDataSetInvalidated();

		// sort each groups
		Iterator<ContactGroup> it = _service.getGroupsList().iterator();
		while (it.hasNext()) {
			ContactGroup g = it.next();
			g.sort(new ContactComparatorOnLineTop());
		}

		_sectionedAdapter.notifyDataSetChanged();
		_noContactForm.update();
	}

	/**
	 * Set the listener when the user clicks on a contact. open a conversation.
	 */
	public void setContactClickListener() {
		_contactList
				.setOnItemClickListener(new AdapterView.OnItemClickListener() {
					@Override
					public void onItemClick(AdapterView<?> av, View v, int pos,
							long id) {
						Object c = _sectionedAdapter.getItem(pos);
						if (c instanceof NetsoulContact)
							launchConversation((NetsoulContact) c);
					}
				});
	}

	/**
	 * Set a callback when user does a long click on a contact TODO: for the
	 * moment the group is not found, which prevents the possibility to delete
	 * the contact
	 */
	public void setContactLongClickListener() {
		_contactList
				.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
					@Override
					public boolean onItemLongClick(AdapterView<?> av, View v,
							int pos, long id) {
						Object c = _sectionedAdapter.getItem(pos);
						if (c instanceof NetsoulContact) {
							ContactGroup g = _service
									.getGroupFromName(_sectionedAdapter
											.getSectionForPos(pos));
							_showContactHandler.showContact((NetsoulContact) c,
									g);
						} else if (c instanceof SectionedAdapter.Section)
							showGroupInfo(c.toString());
						return false;
					}
				});
	}

	private void showGroupInfo(String groupName) {
		final ContactGroup group = _service.getGroupFromName(groupName);
		if (group == null)
			return;

		AlertDialog.Builder alert = new AlertDialog.Builder(this);
		alert.setTitle(group.getName());

		alert.setPositiveButton(R.string.rename, new OnClickListener() {

			@Override
			public void onClick(DialogInterface dialog, int i) {
				showDialogRenameGroup(group);
				updateContactsListAdapter();
				showToast(getString(R.string.group_renamed));
				saveContacts();
			}

		});
		alert.setNeutralButton("Delete", new OnClickListener() {

			@Override
			public void onClick(DialogInterface dialog, int i) {
				_sectionedAdapter.removeSection(group.getName());
				_service.deleteGroup(group);

				// Make sure there is at least one group
				if (_service.getGroupsList().isEmpty()) {
					ContactGroup group = _service
							.addGroup(AndroSoul.DEFAULT_GROUP_NAME);
					if (group != null) {
						ContactAdapterAddGroup(group);
					}
				}

				updateContactsListAdapter();
				showToast(getString(R.string.group_deleted));
				saveContacts();
			}

		});
		alert.setNegativeButton(R.string.cancel, null);

		alert.show();
	}

	/**
	 * Show information about a contact. Triggered after a long press on a
	 * contact
	 * 
	 * @param c
	 *            contact to be showed
	 * @param g
	 *            Group in which the contact belongs
	 */

	@Override
	public boolean onPrepareOptionsMenu(Menu menu) {
		// Change the login / logout button
		// depending on the state of the user
		// TODO: use invalidateOptionsMenu for compatibility with Android 3.0
		MenuItem item = menu.findItem(R.id.b_switch_login);
		if (_service.getConnectionState() == Connection.State.AUTHENTIFIED)
			item.setTitle(R.string.logout);
		else
			item.setTitle(R.string.login);
		return super.onPrepareOptionsMenu(menu);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu) When the
	 * user presses the menu button, show the menu described in
	 * androsoul_menu.xml
	 */
	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.menu.androsoul_menu, menu);
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
	 * Callback when an item in the user menu is selected
	 */
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {
		case R.id.b_configuration:
			launchConfigurationActivity();
			break;
		case R.id.b_newconv:
			showNewConvDialog();
			break;
		case R.id.b_addgroup:
			_addGroupHandler.showDialog();
			break;
		case R.id.b_addcontact:
			_addContactHandler.showDialog();
			break;
		case R.id.b_switch_login:
			switchLogin();
			break;
		case R.id.b_importcontact:
			importContacts();
			break;
		default:
			break;
		}
		return true;
	}

	/**
	 * If user is authenticated, log out, otherwise, try to log in.
	 */
	public void switchLogin() {
		if (_service.getConnectionState() == Connection.State.AUTHENTIFIED) {
			_service.logout();
			clearContactList();
			_messageQueue.clear();
			_loginForm.show();
			_noContactForm.hide();
		} else {
			connect();
		}
	}

	public void clearContactList() {
		_service.clearContacts();
		_sectionedAdapter.clear();
		updateContactsListAdapter();
		_headerView.update();
		_conversationSwitcher.hide();
	}

	/**
	 * Handy method for showing a toast
	 * 
	 * @param content
	 *            Message to be displayed in the toast
	 */
	public void showToast(String content) {
		Toast toast = Toast.makeText(getApplicationContext(), content,
				Toast.LENGTH_SHORT);
		toast.show();
	}

	/**
	 * Launch the configuration menu
	 */
	public void launchConfigurationActivity() {
		Intent i = new Intent(this, ConfigurationActivity.class);
		startActivity(i);
	}

	private void showNewConvDialog() {
		AlertDialog.Builder alert = new AlertDialog.Builder(this);
		alert.setTitle(R.string.group_add).setMessage(R.string.contact_login);
		final EditText input = new EditText(this);
		alert.setView(input);

		alert.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int whichButton) {
				String login = input.getText().toString();
				NetsoulContact contact = _service.addContact(login);
				launchConversation(contact);
			}
		});
		alert.setNegativeButton(R.string.cancel, null);
		alert.show();
	}

	/**
	 * Launch the conversation activity with the given contact
	 * 
	 * @param contact
	 *            Contact related to the conversation
	 */
	public void launchConversation(NetsoulContact contact) {
		Intent i = new Intent(this, ConversationActivity.class);
		i.putExtra(getString(R.string.contact), contact.getName());

		// Update the contact we are talking to
		_talkingTo = contact;
		_service.openConversation(contact);

		startActivity(i);
	}

	/**
	 * Launch a thread that will download every pictures that have not been
	 * previously downloaded. Update the contacts list when done.
	 */
	public void updateContactsPictures() {
		PicturesDownloader pd = new PicturesDownloader(this,
				_service.getGroupsList());
		if (pd.nothingToDownload())
			return;
		pd.set_onFinish(new Runnable() {

			@Override
			public void run() {
				Log.v(AndroSoul.LOGCAT, "Finished downloading pictures");
				_sectionedAdapter.notifyDataSetChanged();
			}
		}, true);
		pd.start();
	}

	private void showProgressDialog(String message) {
		_progressDialog = ProgressDialog.show(this, "", message, true);
		_progressDialog.setCancelable(true);
		_progressDialog.setOnCancelListener(new OnCancelListener() {

			@Override
			public void onCancel(DialogInterface dialog) {
				// TODO
			}

		});
	}

	private void clearProgressDialog() {
		if (_progressDialog == null)
			return;

		_progressDialog.dismiss();
		_progressDialog = null;
	}

	/*
	 * Launch the activity that imports the contacts in the phone
	 */
	public void importContacts() {
		if ((_service != null)
				&& (_service.getConnectionState() == Connection.State.AUTHENTIFIED)) {
			startActivity(new Intent(this, ContactsImportActivity.class));
		} else {
			showToast(getString(R.string.authentify_first));
		}
	}

	public void showDialogChangeGroup(NetsoulContact c) {
		_moveContactHandler.showDialog(c);
	}

	public void showDialogRenameGroup(ContactGroup group) {
		_renameGroupHandler.showDialog(group);
	}
}
